/*
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */
 
/* $Id: netutil.c 35 2007-07-10 05:51:12Z kf701.ye $ */

#include <errno.h>
#include <sys/time.h>
#include <time.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <net/route.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include "kf701.h"

static void inline
set_sockaddr(struct sockaddr_in *sin, uint32_t addr, uint16_t port)
{
	sin->sin_family = AF_INET;
	sin->sin_addr.s_addr = addr;
	sin->sin_port = port;
}

/**
 * @brief open a TCP socket and connect to ip:port
 * @param ip server ip
 * @param port server port
 * @param timeout seconds for time out
 * @retval socket fd success
 * @retval -1 error
 */
int connect_nonblock(char *ip,uint16_t port,uint32_t timeout)
{
	int sockfd, flags, n;
	int sock_error;
	socklen_t len;
	fd_set	rset, wset;
	struct timeval	tval;
	struct sockaddr_in servaddr;	
	struct hostent *hent;

	if (NULL == ip)
		return -1;

	sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if (sockfd < 0) 
		return -1;	
	hent = gethostbyname(ip);
	if (NULL == hent) 
	{
		close(sockfd);
		return -1;
	}
	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons(port);
	servaddr.sin_addr = *(struct in_addr *)hent->h_addr;

	flags = fcntl(sockfd, F_GETFL, 0);
	if (-1 == flags) 
	{
		close(sockfd);
		return -1;
	}
	if ( -1 == fcntl(sockfd, F_SETFL, flags | O_NONBLOCK) )
	{
		close(sockfd);
		return -1;
	}

	if ( (n = connect(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0) )
		if (errno != EINPROGRESS)
		{
			close(sockfd);
			return -1;
		}

	/* Do whatever we want while the connect is taking place. */
	if ( 0 == n)
		return sockfd;	/* connect completed immediately */

	FD_ZERO(&rset);
	FD_SET(sockfd, &rset);
	wset = rset;
	tval.tv_sec = timeout;
	tval.tv_usec = 0;

	if ( select(sockfd+1, &rset, &wset, NULL, timeout ? &tval : NULL) == 0 ) 
	{
		close(sockfd);		/* timeout */		
		return -1;
	}

	if (FD_ISSET(sockfd, &rset) || FD_ISSET(sockfd, &wset)) 
	{		
		len = sizeof(sock_error);
		if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &sock_error, &len) < 0)
		{  
			close(sockfd);			
			return -1;
		}
		if (sock_error)
		{
			errno = sock_error;
			close(sockfd);
			return -1;
		}		
	} else {
		//printf("select error: sockfd not set");
		close(sockfd);
		return -1;
	}
	/* restore file status flags */
	if ( fcntl(sockfd, F_SETFL, flags) < 0 )
	{
		close(sockfd);
		return -1;
	}
	return sockfd;

}

/**
 * @brief open a TCP socket listen on port
 * @param port listen port
 * @retval socket fd success
 * @retval -1 error
 */
int open_listenfd(uint16_t port)
{
	int listenfd, optval = 1;
	struct sockaddr_in serveraddr;

	if( ( listenfd = socket( AF_INET, SOCK_STREAM, 0 )) < 0 )
	{
		return -1;
	}

	if( setsockopt( listenfd, SOL_SOCKET, SO_REUSEADDR, (const void*)&optval, sizeof(int) ) < 0 )
		goto error;

	bzero(( char* )&serveraddr, sizeof(serveraddr) );
	serveraddr.sin_family = AF_INET;
	serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
	serveraddr.sin_port = htons((uint16_t)port);

	if( bind( listenfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr) ) < 0 )
	{
		goto error ;
	}

	if( listen( listenfd, 5 ) < 0 )
		goto error;

	return listenfd;
error:
	close(listenfd);
	return -1;
}

/**
 * @brief open a UDP socket listen on port
 * @param port listen port
 * @retval socket fd success
 * @retval -1 error
 */
int open_udp( uint16_t port)
{
	int fd;
	fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
	if ( fd == -1 )
	{
		sys_message("%s,%d:socket error\n",__func__,__LINE__);
		return -1;
	}

	struct sockaddr_in addr;
	memset((char*) &(addr),0, sizeof((addr)));
	addr.sin_family = AF_INET;
	addr.sin_addr.s_addr = htonl(INADDR_ANY);
	addr.sin_port = htons(port);

	if ( bind( fd,(struct sockaddr*)&addr, sizeof(addr)) != 0 )
	{
		switch (errno)
		{
			case 0:
				{
					sys_message("%s,%d:bind error\n",__func__,__LINE__);
					return -1;
				}
			case EADDRINUSE:
				{
					sys_message("%s,%d:port in use\n",__func__,__LINE__);
					return -1;
				}
				break;
			case EADDRNOTAVAIL:
				{
					sys_message("%s,%d:Cannot assign requested address\n",__func__,__LINE__);
					return -1;
				}
				break;
			default:
				{
					sys_message("%s,%d:error=%s\n",__func__,__LINE__,strerror(errno));
					return -1;
				}
				break;
		}
	}
	return fd;
}

/**
 * @brief add gw to route table
 * @param gateway_ip
 */
bool setup_route( char *gateway_ip )
{
	struct in_addr gw;
	inet_aton( gateway_ip, &gw );

	struct rtentry rm;
	memset(&rm, 0, sizeof(rm));

	set_sockaddr((struct sockaddr_in *) &rm.rt_dst, 0, 0);
	set_sockaddr((struct sockaddr_in *) &rm.rt_genmask, 0, 0);
	set_sockaddr((struct sockaddr_in *) &rm.rt_gateway, gw.s_addr, 0);
	rm.rt_flags = RTF_UP | RTF_GATEWAY;

	int sockfd = socket(AF_INET,SOCK_DGRAM,0);
	if (ioctl(sockfd, SIOCADDRT, &rm) < 0)
	{
		sys_message("%s,%d: %m\n",__func__,__LINE__);
		close( sockfd );
		return false;
	}
	close( sockfd );
	return true;
}

/**
 * @brief delete gw from route table
 * @param gateway_ip
 */
bool delete_route( char *gateway_ip )
{
	struct in_addr gw;
	inet_aton( gateway_ip, &gw );

	struct rtentry rm;
	memset(&rm, 0, sizeof(rm));

	set_sockaddr((struct sockaddr_in *) &rm.rt_dst, 0, 0);
	set_sockaddr((struct sockaddr_in *) &rm.rt_genmask, 0, 0);
	set_sockaddr((struct sockaddr_in *) &rm.rt_gateway, gw.s_addr, 0);
	rm.rt_flags = RTF_UP | RTF_GATEWAY;

	int sockfd = socket(AF_INET,SOCK_DGRAM,0);
	if (ioctl(sockfd, SIOCDELRT, &rm) < 0)
	{
		sys_message("%s,%d: %m\n",__func__,__LINE__);
		close( sockfd );
		return false;
	}
	close( sockfd );
	return true;
}

#define IPV4_MASK (RTF_GATEWAY|RTF_HOST|RTF_REINSTATE|RTF_DYNAMIC|RTF_MODIFIED)
#define IPV6_MASK (RTF_GATEWAY|RTF_HOST|RTF_DEFAULT|RTF_ADDRCONF|RTF_CACHE)

static const unsigned int flagvals[] = { /* Must agree with flagchars[]. */
	RTF_GATEWAY,
	RTF_HOST,
	RTF_REINSTATE,
	RTF_DYNAMIC,
	RTF_MODIFIED,
#ifdef CONFIG_FEATURE_IPV6
	RTF_DEFAULT,
	RTF_ADDRCONF,
	RTF_CACHE
#endif
};

static const char flagchars[] =         /* Must agree with flagvals[]. */
	"GHRDM"
#ifdef CONFIG_FEATURE_IPV6
	"DAC"
#endif
;

/**
 * @brief convert route flags to readable char
 */
static void set_flags(char *flagstr, int flags)
{
	int i;

	*flagstr++ = 'U';

	for (i=0 ; (*flagstr = flagchars[i]) != 0 ; i++)
	{
		if (flags & flagvals[i])
		{
			++flagstr;
		}
	}
}

/**
 * @brief display ipv4 route table
 */
void display_route(void)
{
	char devname[64], flags[16], sdest[16], sgw[16], smask[16];
	unsigned long int d, g, m;
	int flgs, ref, use, metric, mtu, win, ir;
	struct in_addr mask;

	FILE *fp = fopen("/proc/net/route", "r");

	sys_message("Destination     Gateway         Genmask         Flags    Iface\n");

	if (fscanf(fp, "%*[^\n]\n") < 0)
	{ /* Skip the first line. */
		goto ERROR_RET;
	}
	while (1)
	{
		int r;
		r = fscanf(fp, "%63s%lx%lx%X%d%d%d%lx%d%d%d\n",
				devname, &d, &g, &flgs, &ref, &use, &metric, &m,
				&mtu, &win, &ir);
		if (r != 11)
		{
			if ((r < 0) && feof(fp))
			{ /* EOF with no (nonspace) chars read. */
				break;
			}
		}
		if (!(flgs & RTF_UP))
		{ /* Skip interfaces that are down. */
			continue;
		}

		set_flags(flags, (flgs & IPV4_MASK));

		mask.s_addr = d;
		strncpy( sdest, inet_ntoa(mask), sizeof(sdest) );
		mask.s_addr = g;
		strncpy( sgw, inet_ntoa(mask), sizeof(sgw) );
		mask.s_addr = m;
		strncpy( smask, inet_ntoa(mask), sizeof(smask) );

		sys_message("%-16s%-16s%-16s%-6s   %s\n", sdest, sgw, smask, flags, devname);
	}
ERROR_RET:
	fclose (fp);
}


